在結束上一篇30天Flutter手滑系列 - 布局組件(Layout Widgets)(下)後,基礎的UI概念應該已經有了,接下來會介紹同樣很重要的導航與路由。
很多時候不管在做Web或Mobile,都需要做跳轉頁面的功能。基本的場景就是當點擊一個連結後,當前頁面會被跳轉到另一個頁面去,例如點擊註冊按鈕後會從首頁/
跳到註冊頁 /register
。
而這些被定義的/
、/home
、/register
名稱都是路由(Router)系統的一部分。
管理這些Route的進出,就是導航(Navigator),在這裡也是一個Navigator Widget。
用來管理進出頁面的機制,本身是一個Stack
(堆疊),利用push
和pop
操作這個堆疊裡的目標。
假設我有一個App,首先我點擊註冊按鈕 -> 點擊返回上一頁 -> 點擊登入按鈕,這時候的Stack其內容的變化會是如下圖所示。
下面實做一個跳轉的畫面:
push
到Stack。pop
出去import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: RaisedButton(
child: Text('Register'),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => RegisterPage()));
},
),
),
);
}
}
class RegisterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Register Page'),
),
body: Center(),
);
}
}
路由是對頁面的抽象。
簡單來說,每一頁面都有對應的識別名稱,例如/home
、/login
、/register
,讓使用者可以被引導到正確的頁面。
路由又分成兩種:
需要直接註冊路由名稱,不能夠被傳遞參數,但可以接收下一頁返回的值。
我們修改上面的範例,加入routes,並在裡面定義一個/register
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
routes: <String, WidgetBuilder>{'/register': (_) => new RegisterPage()},
);
}
}
然後在onPressed
事件稍做修改,改調用Navigator.pushNamed
這個方法,內容的字串需要跟routes裡面的名稱完全符合。
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: RaisedButton(
child: Text('Register'),
onPressed: () {
Navigator.of(context).pushNamed('/register');
},
),
),
);
}
}
這個結果是跟上面只用push
的效果是一樣依樣的。
可以傳遞參數,需要自己創建實例。
在這裡示範由HomePage傳遞一段字串到下一頁的效果,然後返回另一段字串內容。
首先,我們修改上一個範例,在pushNamed
加入第二個參數arguments
,這邊傳遞一個靜態的物件到下一個路由。
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: RaisedButton(
child: Text('Register'),
onPressed: () {
Navigator.of(context).pushNamed('/register',
arguments: {'name': 'Raymond'}).then((value) {
// 新增第二個變數arguments
showDialog(
// 新增一個對話框,用來顯示回傳的值
context: context,
child: AlertDialog(
content: Text(value),
));
});
},
),
),
);
}
}
在RegisterPage先新增一個變數,用來儲存接收到的參數,然後新增一個按鈕,讓按下按鈕時,可以回傳一組字串。
class RegisterPage extends StatelessWidget {
String name; // 宣告一個字串變數
@override
Widget build(BuildContext context) {
dynamic obj = ModalRoute.of(context).settings.arguments;
name = obj["name"]; // 把接收到的參數存到變數
return Scaffold(
appBar: AppBar(
title: Text('Register Page'),
),
body: Center(
child: RaisedButton(
child: Text('Confirm'),
onPressed: () {
Navigator.of(context).pop("Hello ${name}"); // 當按下按鈕會返回上一頁,並回傳內容
},
),
),
);
}
}
在MyHomePage的Navigator
加入then
的方法,在裡面新增一個showDialog
,用來顯示接收到的回傳值。
路由跳轉是很常見的功能,不過概念上就是push跟pop而已。
倒是稍早有查到一篇文章Flutter早知道 - Named Router可以传参了!,裡面提到pushNamed
在某個開發版的branch已經可以傳遞參數,但我還沒實驗這部分,如果已經有人有測試過,再麻煩分享一下,或是等我晚點測試看看。
https://api.flutter.dev/flutter/widgets/Navigator-class.html
https://blog.whezh.com/flutter-route-and-navigator/
https://medium.com/flutter-community/flutter-navigation-cheatsheet-a-guide-to-named-routing-dc642702b98c